<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>（Vue源码五）Vue 编译原理 | Jeffery Blog</title><meta name="keywords" content="vue, 编译原理，vue 源码"><meta name="author" content="志非"><meta name="copyright" content="志非"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="vue, vue 编译原理">
<meta property="og:type" content="article">
<meta property="og:title" content="（Vue源码五）Vue 编译原理">
<meta property="og:url" content="https://fif.gitee.io/blog/2021/04/30/vue/vue-code-7-compiler/index.html">
<meta property="og:site_name" content="Jeffery Blog">
<meta property="og:description" content="vue, vue 编译原理">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://gitee.com/FIF/pic-beg/raw/master/images/vue/vuetemplate.jpg">
<meta property="article:published_time" content="2021-04-29T16:00:00.000Z">
<meta property="article:modified_time" content="2021-05-07T16:00:00.000Z">
<meta property="article:author" content="志非">
<meta property="article:tag" content="vue">
<meta property="article:tag" content="vue 源码">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://gitee.com/FIF/pic-beg/raw/master/images/vue/vuetemplate.jpg"><link rel="shortcut icon" href="/blog/img/home/avatar.jpeg"><link rel="canonical" href="https://fif.gitee.io/blog/2021/04/30/vue/vue-code-7-compiler/"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//fonts.googleapis.com" crossorigin=""/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/blog/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/node-snackbar/dist/snackbar.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/instantsearch.js@2.10.5/dist/instantsearch.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/instantsearch.js@2.10.5/dist/instantsearch.min.js" defer></script><script async="async" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script><script>(adsbygoogle = window.adsbygoogle || []).push({
  google_ad_client: '',
  enable_page_level_ads: 'true'
});</script><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Titillium+Web&amp;display=swap" media="print" onload="this.media='all'"><script>const GLOBAL_CONFIG = { 
  root: '/blog/',
  algolia: {"appId":"QYKLQ4BSGP","apiKey":"d45fc66694b4260b20711a30fdb5c4e1","indexName":"jeffery-blog","hits":{"per_page":6},"languages":{"input_placeholder":"搜索文章","hits_empty":"找不到您查询的内容：${query}","hits_stats":"找到 ${hits} 条结果，用时 ${time} 毫秒"}},
  localSearch: undefined,
  translate: {"defaultEncoding":2,"translateDelay":0,"msgToTraditionalChinese":"繁","msgToSimplifiedChinese":"簡"},
  noticeOutdate: {"limitDay":200,"position":"top","messagePrev":"本文修改于","messageNext":"天前，如果本文有一定的时效性，那么本文可能已经失效~"},
  highlight: {"plugin":"highlighjs","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":false},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: true,
    post: false
  },
  runtime: '天',
  date_suffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: undefined,
  lightbox: 'mediumZoom',
  Snackbar: {"chs_to_cht":"你已切换为繁体","cht_to_chs":"你已切换为简体","day_to_night":"你已切换为深色模式","night_to_day":"你已切换为浅色模式","bgLight":"#49b1f5","bgDark":"#121212","position":"bottom-left"},
  source: {
    jQuery: 'https://cdn.jsdelivr.net/npm/jquery@latest/dist/jquery.min.js',
    justifiedGallery: {
      js: 'https://cdn.jsdelivr.net/npm/justifiedGallery/dist/js/jquery.justifiedGallery.min.js',
      css: 'https://cdn.jsdelivr.net/npm/justifiedGallery/dist/css/justifiedGallery.min.css'
    },
    fancybox: {
      js: 'https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@latest/dist/jquery.fancybox.min.js',
      css: 'https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@latest/dist/jquery.fancybox.min.css'
    }
  },
  isPhotoFigcaption: false,
  islazyload: true,
  isanchor: false
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = { 
  isPost: true,
  isHome: false,
  isHighlightShrink: true,
  isToc: true,
  postUpdate: '2021-05-08 00:00:00'
}</script><noscript><style type="text/css">
  #nav {
    opacity: 1
  }
  .justified-gallery img {
    opacity: 1
  }

  #recent-posts time,
  #post-meta time {
    display: inline !important
  }
</style></noscript><script>(win=>{
    win.saveToLocal = {
      set: function setWithExpiry(key, value, ttl) {
        if (ttl === 0) return
        const now = new Date()
        const expiryDay = ttl * 86400000
        const item = {
          value: value,
          expiry: now.getTime() + expiryDay,
        }
        localStorage.setItem(key, JSON.stringify(item))
      },

      get: function getWithExpiry(key) {
        const itemStr = localStorage.getItem(key)

        if (!itemStr) {
          return undefined
        }
        const item = JSON.parse(itemStr)
        const now = new Date()

        if (now.getTime() > item.expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return item.value
      }
    }
  
    win.getScript = url => new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = url
      script.async = true
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    })
  
      win.activateDarkMode = function () {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      win.activateLightMode = function () {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }
      const t = saveToLocal.get('theme')
    
          if (t === 'dark') activateDarkMode()
          else if (t === 'light') activateLightMode()
        
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        if (asideStatus === 'hide') {
          document.documentElement.classList.add('hide-aside')
        } else {
          document.documentElement.classList.remove('hide-aside')
        }
      }
    })(window)</script><meta name="generator" content="Hexo 5.4.0"><link rel="alternate" href="/blog/atom.xml" title="Jeffery Blog" type="application/atom+xml">
</head><body><div id="loading-box"><div class="loading-left-bg"></div><div class="loading-right-bg"></div><img class="load-image" src= "" data-lazy-src="/blog/img/loading.gif" alt=""/></div><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="author-avatar"><img class="avatar-img" src= "" data-lazy-src="/blog/img/home/avatar.jpeg" onerror="onerror=null;src='/img/friend_404.gif'" alt="avatar"/></div><div class="site-data"><div class="data-item is-center"><div class="data-item-link"><a href="/blog/archives/"><div class="headline">文章</div><div class="length-num">12</div></a></div></div><div class="data-item is-center"><div class="data-item-link"><a href="/blog/tags/"><div class="headline">标签</div><div class="length-num">6</div></a></div></div><div class="data-item is-center"><div class="data-item-link"><a href="/blog/categories/"><div class="headline">分类</div><div class="length-num">7</div></a></div></div></div><hr/><div class="menus_items"><div class="menus_item"><a class="site-page" href="/blog/"><i class="fa-fw fa fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/blog/archives/"><i class="fa-fw fa fa-archive"></i><span> 时间轴</span></a></div><div class="menus_item"><a class="site-page" href="/blog/tags/"><i class="fa-fw fa fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/blog/categories/"><i class="fa-fw fa fa-folder-open"></i><span> 分类</span></a></div><div class="menus_item"><a class="site-page" href="javascript:void(0);"><i class="fa-fw fas fa-list"></i><span> 媒体</span><i class="fas fa-chevron-down expand"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/blog/books/"><i class="fa-fw fas fa-book"></i><span> 阅读</span></a></li><li><a class="site-page child" href="/blog/music/"><i class="fa-fw fa fa-music"></i><span> 音乐</span></a></li><li><a class="site-page child" href="/blog/movies/"><i class="fa-fw fa fa-film"></i><span> 电影</span></a></li><li><a class="site-page child" href="/blog/gallery/"><i class="fa-fw far fa-image"></i><span> 相册</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="/blog/box/"><i class="fa-fw fab fa-xbox"></i><span> 聚宝盒</span></a></div><div class="menus_item"><a class="site-page" href="/blog/talk/"><i class="fa-fw far fa-comment"></i><span> 微语</span></a></div><div class="menus_item"><a class="site-page" href="/blog/link/"><i class="fa-fw fa fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/blog/about/"><i class="fa-fw fa fa-heart"></i><span> 关于</span></a></div></div></div></div><div class="post" id="body-wrap"><header class="post-bg" id="page-header" style="background-image: url('https://gitee.com/FIF/pic-beg/raw/master/images/vue/vuetemplate.jpg')"><nav id="nav"><span id="blog_name"><a id="site-name" href="/blog/">Jeffery Blog</a></span><div id="menus"><div id="search-button"><a class="site-page social-icon search"><i class="fas fa-search fa-fw"></i><span> 搜索</span></a></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="/blog/"><i class="fa-fw fa fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/blog/archives/"><i class="fa-fw fa fa-archive"></i><span> 时间轴</span></a></div><div class="menus_item"><a class="site-page" href="/blog/tags/"><i class="fa-fw fa fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/blog/categories/"><i class="fa-fw fa fa-folder-open"></i><span> 分类</span></a></div><div class="menus_item"><a class="site-page" href="javascript:void(0);"><i class="fa-fw fas fa-list"></i><span> 媒体</span><i class="fas fa-chevron-down expand"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/blog/books/"><i class="fa-fw fas fa-book"></i><span> 阅读</span></a></li><li><a class="site-page child" href="/blog/music/"><i class="fa-fw fa fa-music"></i><span> 音乐</span></a></li><li><a class="site-page child" href="/blog/movies/"><i class="fa-fw fa fa-film"></i><span> 电影</span></a></li><li><a class="site-page child" href="/blog/gallery/"><i class="fa-fw far fa-image"></i><span> 相册</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="/blog/box/"><i class="fa-fw fab fa-xbox"></i><span> 聚宝盒</span></a></div><div class="menus_item"><a class="site-page" href="/blog/talk/"><i class="fa-fw far fa-comment"></i><span> 微语</span></a></div><div class="menus_item"><a class="site-page" href="/blog/link/"><i class="fa-fw fa fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/blog/about/"><i class="fa-fw fa fa-heart"></i><span> 关于</span></a></div></div><div id="toggle-menu"><a class="site-page"><i class="fas fa-bars fa-fw"></i></a></div></div></nav><div id="post-info"><h1 class="post-title">（Vue源码五）Vue 编译原理</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2021-04-29T16:00:00.000Z" title="发表于 2021-04-30 00:00:00">2021-04-30</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2021-05-07T16:00:00.000Z" title="更新于 2021-05-08 00:00:00">2021-05-08</time></span><span class="post-meta-categories"><span class="post-meta-separator">|</span><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/blog/categories/%E5%89%8D%E7%AB%AF/">前端</a><i class="fas fa-angle-right post-meta-separator"></i><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/blog/categories/%E5%89%8D%E7%AB%AF/vue/">vue</a><i class="fas fa-angle-right post-meta-separator"></i><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/blog/categories/%E5%89%8D%E7%AB%AF/vue/vue-%E6%BA%90%E7%A0%81/">vue 源码</a></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-wordcount"><i class="far fa-file-word fa-fw post-meta-icon"></i><span class="post-meta-label">字数总计:</span><span class="word-count">4.8k</span><span class="post-meta-separator">|</span><i class="far fa-clock fa-fw post-meta-icon"></i><span class="post-meta-label">阅读时长:</span><span>18分钟</span></span><span class="post-meta-separator">|</span><span class="post-meta-pv-cv" id="" data-flag-title="（Vue源码五）Vue 编译原理"><i class="far fa-eye fa-fw post-meta-icon"></i><span class="post-meta-label">阅读量:</span><span id="busuanzi_value_page_pv"></span></span></div></div></div></header><main class="layout" id="content-inner"><div id="post"><article class="post-content" id="article-container"><h2 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h2><p>平时使用模板时，可以在模板中使用变量、表达式或者指令等，如 <code>v-on</code>、<code>v-if</code>，这些语法在 <code>html</code> 中是不存在的，那 <code>vue</code> 中为什么可以实现？这就归功于模板编译功能。</p>
<p><code>Vue</code> 内置的编译器或 <code>vue-loader</code> 插件带有编译功能，它把<code>&lt;template&gt;&lt;/template&gt;</code>标签中类似于原生<code>HTML</code>的内容进行编译，本质上通过正则匹配，生成渲染函数，也就是<code>render</code>函数，而<code>render</code>函数会将模板内容生成对应的<code>VNode</code>，而<code>VNode</code>再经过<code>patch</code>过程创建真实的<code>DOM</code>节点并插入到视图中， 最终完成视图的渲染更新。</p>
<p><img src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/1620553289128-templatef1b4afd6821eaa43f866765b702c6675.png"></p>
<p>从图中我们也可以看到，模板编译过程就是把用户写的模板经过一系列处理最终生成<code>render</code>函数的过程。</p>
<h2 id="模板渲染过程"><a href="#模板渲染过程" class="headerlink" title="模板渲染过程"></a>模板渲染过程</h2><p><img src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/vuetemplate.jpg"></p>
<p>从执行流程图中我们可以知道，在 <code>$mount</code> 挂载会执行 <code>compileToFunctions</code> 方法，<code>compile</code> 进行模板编译,  编译过程主要做了三件事：</p>
<ul>
<li><code>parse</code> :  通过正则匹配解析模板字符串生成 <code>ast</code>，它是一个抽象语法树，<a target="_blank" rel="noopener" href="https://astexplorer.net/">ast在线演示工具</a></li>
<li><code>optimize</code>: 优化语法树， 打上静态节点或根静态节点标识</li>
<li><code>generate</code>:  <code>ast</code> 转化为可执行代码</li>
</ul>
<p>在 <code>Vue</code> 源码中实现编译过程没有这么直观，为了兼容不同的编译平台，它使用了函数柯里化的技巧，有点绕，不过只要记住上面是编译的核心方法，接下来分析它的源码。</p>
<p>编译结果可以借助这个在线工具 <a target="_blank" rel="noopener" href="https://template-explorer.vuejs.org/">模版在线编译工具</a></p>
<h2 id="编译入口"><a href="#编译入口" class="headerlink" title="编译入口"></a>编译入口</h2><p>当我们使用<code> Runtime + Compiler</code> 的 Vue.js，它的入口是 <code>src/platforms/web/entry-runtime-with-compiler.js</code>，看一下它对 <code>$mount</code> 函数的定义：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 缓存 $mount</span></span><br><span class="line"><span class="keyword">const</span> mount = Vue.prototype.$mount</span><br><span class="line"><span class="comment">// 重新定义 $mount，实现编译功能</span></span><br><span class="line">Vue.prototype.$mount = <span class="function"><span class="keyword">function</span> (<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  el?: string | Element,</span></span></span><br><span class="line"><span class="function"><span class="params">  hydrating?: boolean</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">Component</span> </span>&#123;</span><br><span class="line">  <span class="comment">//... 模板编译</span></span><br><span class="line">  <span class="keyword">const</span> &#123; render, staticRenderFns &#125; = compileToFunctions(template, &#123;</span><br><span class="line">        shouldDecodeNewlines,</span><br><span class="line">        shouldDecodeNewlinesForHref,</span><br><span class="line">        delimiters: options.delimiters,</span><br><span class="line">        comments: options.comments</span><br><span class="line">      &#125;, <span class="built_in">this</span>)</span><br><span class="line">      options.render = render</span><br><span class="line">      options.staticRenderFns = staticRenderFns</span><br><span class="line">  <span class="comment">//...</span></span><br><span class="line">  <span class="keyword">return</span> mount.call(<span class="built_in">this</span>, el, hydrating)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>compileToFunctions</code> 方法是一个编译器，把模板 <code>template</code> 编译生成 <code>render</code> 以及 <code>staticRenderFns</code>，它的定义在 <code>src/platforms/web/compiler/index.js</code> 中：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; baseOptions &#125; <span class="keyword">from</span> <span class="string">&#x27;./options&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; createCompiler &#125; <span class="keyword">from</span> <span class="string">&#x27;compiler/index&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> &#123; compile, compileToFunctions &#125; = createCompiler(baseOptions)</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> &#123; compile, compileToFunctions &#125;</span><br></pre></td></tr></table></figure>

<p>可以看到 <code>compileToFunctions</code> 方法实际上是 <code>createCompiler</code> 方法的返回值，该方法接收一个编译配置参数，接下来我们来看一下 <code>createCompiler</code> 方法的定义，在 <code>src/compiler/index.js</code> 中：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> createCompiler = createCompilerCreator(<span class="function"><span class="keyword">function</span> <span class="title">baseCompile</span> (<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  template: string,</span></span></span><br><span class="line"><span class="function"><span class="params">  options: CompilerOptions</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">CompiledResult</span> </span>&#123;</span><br><span class="line">  <span class="comment">// 解析器 template =&gt; ast</span></span><br><span class="line">  <span class="keyword">const</span> ast = parse(template.trim(), options)</span><br><span class="line">  <span class="comment">// 优化器，打上静态节点或根静态节点标识</span></span><br><span class="line">  <span class="keyword">if</span> (options.optimize !== <span class="literal">false</span>) &#123;</span><br><span class="line">    optimize(ast, options)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 代码生成器， ast =&gt; render 函数</span></span><br><span class="line">  <span class="keyword">const</span> code = generate(ast, options)</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    ast,</span><br><span class="line">    render: code.render,</span><br><span class="line">    staticRenderFns: code.staticRenderFns</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p><code>createCompiler</code> 方法实际上是通过调用 <code>createCompilerCreator</code> 方法返回的，该方法传入的参数是一个函数，真正的编译过程都在这个 <code>baseCompile</code> 函数里执行，那么 <code>createCompilerCreator</code> 又是什么呢，它的定义在 <code>src/compiler/create-compiler.js</code> 中：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">createCompilerCreator</span> (<span class="params">baseCompile: <span class="built_in">Function</span></span>): <span class="title">Function</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> <span class="title">createCompiler</span> (<span class="params">baseOptions: CompilerOptions</span>) </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">compile</span> (<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">      template: string,</span></span></span><br><span class="line"><span class="function"><span class="params">      options?: CompilerOptions</span></span></span><br><span class="line"><span class="function"><span class="params">    </span>): <span class="title">CompiledResult</span> </span>&#123;</span><br><span class="line">      <span class="comment">// ... 执行编译</span></span><br><span class="line">      <span class="keyword">const</span> compiled = baseCompile(template, finalOptions)</span><br><span class="line">      <span class="comment">// ...</span></span><br><span class="line">      <span class="keyword">return</span> compiled</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      compile,</span><br><span class="line">      compileToFunctions: createCompileToFunctionFn(compile)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>该方法返回了一个 <code>createCompiler</code> 的函数，它接收一个 <code>baseOptions</code> 的参数，返回的是一个对象，包括 <code>compile</code> 方法属性和 <code>compileToFunctions</code> 属性，这个 <code>compileToFunctions</code> 对应的就是 <code>$mount</code> 函数调用的 <code>compileToFunctions</code> 方法，它是调用 <code>createCompileToFunctionFn</code> 方法的返回值，我们接下来看一下 <code>createCompileToFunctionFn</code> 方法，它的定义在 <code>src/compiler/to-function/js</code> 中：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">createCompileToFunctionFn</span> (<span class="params">compile: <span class="built_in">Function</span></span>): <span class="title">Function</span> </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> cache = <span class="built_in">Object</span>.create(<span class="literal">null</span>)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> <span class="title">compileToFunctions</span> (<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">    template: string,</span></span></span><br><span class="line"><span class="function"><span class="params">    options?: CompilerOptions,</span></span></span><br><span class="line"><span class="function"><span class="params">    vm?: Component</span></span></span><br><span class="line"><span class="function"><span class="params">  </span>): <span class="title">CompiledFunctionResult</span> </span>&#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// check cache 缓存编译结果</span></span><br><span class="line">    <span class="keyword">const</span> key = options.delimiters</span><br><span class="line">      ? <span class="built_in">String</span>(options.delimiters) + template</span><br><span class="line">      : template</span><br><span class="line">    <span class="keyword">if</span> (cache[key]) &#123;</span><br><span class="line">      <span class="keyword">return</span> cache[key]</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// compile 编译</span></span><br><span class="line">    <span class="keyword">const</span> compiled = compile(template, options)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 实例化为可执行函数</span></span><br><span class="line">    res.render = createFunction(compiled.render, fnGenErrors)</span><br><span class="line">    res.staticRenderFns = compiled.staticRenderFns.map(<span class="function"><span class="params">code</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> createFunction(code, fnGenErrors)</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> (cache[key] = res)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>compileToFunctions</code> 的最终定义，它接收 3 个参数、编译模板 <code>template</code>，编译配置 <code>options</code> 和 Vue 实例 <code>vm</code>。这也是我们在<code>$mount</code> 真正执行的方法，它核心的编译过程就一行代码：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> compiled = compile(template, options)</span><br></pre></td></tr></table></figure>

<p>接下来我们捋一下 <code>compileToFunctions</code> 执行流程：</p>
<ul>
<li><code>$mount</code> 方法 -》 执行 <code>compileToFunctions</code> </li>
<li><code>compileToFunctions</code>  -》 执行 <code>compile</code> ，它是一个函数参数</li>
<li><code>compile</code> -》执行 <code>baseCompile</code></li>
<li><code>baseCompile</code> -》 执行 <code>parse, optimize, generate</code> -》返回 <code>render</code></li>
</ul>
<p>所以经过层层方法执行，最终编译结果是 <code>render</code> 函数</p>
<h2 id="具体流程"><a href="#具体流程" class="headerlink" title="具体流程"></a>具体流程</h2><p>从上面可以看出执行流程比较绕，我们从新梳理一下。</p>
<p>编译过程将字符串模板解析成抽象语法树<code>AST</code>后，<code>AST</code> 来生成<code>render</code>函数。其具体流程可大致分为三个阶段：</p>
<ul>
<li>模板解析阶段：将模板字符串用正则等方式解析成抽象语法树<code>AST</code>——解析器——源码路径：<code>src/compiler/parser/index.js</code></li>
<li>优化阶段：遍历<code>AST</code>，找出其中的静态节点，并打上标记；——优化器——源码路径：<code>src/compiler/optimizer.js</code>;</li>
<li>代码生成阶段：将<code>AST</code>转换成渲染函数； 这三个阶段在源码中分别对应三个模块——代码生成器——源码路径：<code>src/compiler/codegen/index.js</code></li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 源码位置: /src/complier/index.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> createCompiler = createCompilerCreator(<span class="function"><span class="keyword">function</span> <span class="title">baseCompile</span> (<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  template: string,</span></span></span><br><span class="line"><span class="function"><span class="params">  options: CompilerOptions</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">CompiledResult</span> </span>&#123;</span><br><span class="line">  <span class="comment">// 模板解析阶段：用正则等方式解析 template 模板中的指令、class、style等数据，形成AST</span></span><br><span class="line">  <span class="keyword">const</span> ast = parse(template.trim(), options)</span><br><span class="line">  <span class="keyword">if</span> (options.optimize !== <span class="literal">false</span>) &#123;</span><br><span class="line">    <span class="comment">// 优化阶段：遍历AST，找出其中的静态节点，并打上标记；</span></span><br><span class="line">    optimize(ast, options)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 代码生成阶段：将AST转换成渲染函数；</span></span><br><span class="line">  <span class="keyword">const</span> code = generate(ast, options)</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    ast,</span><br><span class="line">    render: code.render,</span><br><span class="line">    staticRenderFns: code.staticRenderFns</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>可以看到 <code>baseCompile</code> 的代码非常的简短主要核心代码。</p>
<ul>
<li><strong>const ast =parse(template.trim(), options)</strong>:<code>parse</code> 会用正则等方式解析 <code>template</code> 模板中的指令、<code>class</code>、<code>style</code>等数据，形成<code>AST</code>。</li>
<li><strong>optimize(ast, options)</strong>: <code>optimize</code> 的主要作用是标记静态节点，这是 <code>Vue</code> 在编译过程中的一处优化，当在进行<code>patch</code> 的过程中， <code>DOM-Diff</code> 算法会直接跳过静态节点，从而减少了比较的过程，优化了 <code>patch</code> 的性能。</li>
<li><strong>const code =generate(ast, options)</strong>: 将 <code>AST</code> 转化成 <code>render</code>函数字符串的过程，得到结果是 <code>render</code>函数 的字符串以及 <code>staticRenderFns</code> 字符串。 最终 <code>baseCompile</code> 的返回值</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">     ast: ast,</span><br><span class="line">     render: code.render,</span><br><span class="line">     staticRenderFns: code.staticRenderFns</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>

<p>最终返回了抽象语法树( ast )，渲染函数( render )，静态渲染函数( staticRenderFns )，且<code>render</code> 的值为<code>code.render</code>，<code>staticRenderFns</code> 的值为<code>code.staticRenderFns</code>，也就是说通过 <code>generate</code>处理 <code>ast</code>之后得到的返回值 <code>code</code> 是一个对象。</p>
<p>下面再给出模板编译内部具体流程图，便于理解。流程图如下：</p>
<p><img src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/1620557987109-9b32f7dc778367c251479fe8fb9b07f7.png" alt="综述 - 图3"></p>
<h2 id="parse-解析器"><a href="#parse-解析器" class="headerlink" title="parse 解析器"></a>parse 解析器</h2><p><code>parse</code> 解析器将 <code>template</code> 模版通过正则匹配转化为 <code>ast</code> 抽象语法树，解析器除了解析 <code>html</code> 标签，还会解析文本和过滤器，所以解析器包含 <code>html</code> 解析器，文本解析器和过滤器解析器。</p>
<p>在整个模版解析过程中，<code>HTML</code> 解析器是主线，先用 <code>HTML</code> 解析器进行解析整个模板，在解析过程中如果碰到文本内容，那就调用文本解析器来解析文本，如果碰到文本中包含过滤器那就调用过滤器解析器来解析。</p>
<h3 id="parse-源码分析"><a href="#parse-源码分析" class="headerlink" title="parse 源码分析"></a>parse 源码分析</h3><p>解析器的源码位于<code>/src/complier/parser</code>文件夹下，其主线代码如下：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 代码位置：/src/complier/parser/index.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">parse</span> (<span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params">  template: string,</span></span></span><br><span class="line"><span class="function"><span class="params">  options: CompilerOptions</span></span></span><br><span class="line"><span class="function"><span class="params"></span>): <span class="title">ASTElement</span> | <span class="title">void</span> </span>&#123;</span><br><span class="line">  getFnsAndConfigFromOptions(options)</span><br><span class="line"></span><br><span class="line">  parseHTML(template, &#123;</span><br><span class="line">    <span class="comment">// options ...</span></span><br><span class="line">    <span class="comment">// 解析开始标签，如：&lt;div id=&quot;app&quot;&gt;</span></span><br><span class="line">    start (tag, attrs, unary) &#123;</span><br><span class="line">      <span class="keyword">let</span> element = createASTElement(tag, attrs)</span><br><span class="line">      processElement(element)</span><br><span class="line">      treeManagement()</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 解析结束标签，如：&lt;/div&gt;</span></span><br><span class="line">    end () &#123;</span><br><span class="line">      treeManagement()</span><br><span class="line">      closeElement()</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 解析文本字符串</span></span><br><span class="line">    chars (text: string) &#123;</span><br><span class="line">      handleText()</span><br><span class="line">      createChildrenASTOfText()</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 解析注释节点，如：&lt;!-- 注释 --&gt;</span></span><br><span class="line">    comment (text: string) &#123;</span><br><span class="line">      createChildrenASTOfComment()</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line">  <span class="keyword">return</span> astRootElement</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>从上面代码中可以看到，<code>parse</code> 函数就是解析器的主函数，在<code>parse</code> 函数内调用了<code>parseHTML</code> 函数对模板字符串进行解析，在<code>parseHTML</code> 函数解析模板字符串的过程中，如果遇到文本信息，就会调用文本解析器<code>parseText</code>函数进行文本解析；如果遇到文本中包含过滤器，就会调用过滤器解析器<code>parseFilters</code>函数进行解析。</p>
<p><code>parse</code> 还包含几个配置函数 <code>start</code>, <code>end</code>, <code>chars</code>, <code>comment</code> 分别是处理开始标签、结束标签、文本、和注释文本</p>
<h3 id="parseHTML-（HTML解析器）"><a href="#parseHTML-（HTML解析器）" class="headerlink" title="parseHTML （HTML解析器）"></a>parseHTML （HTML解析器）</h3><p>对于 <code>template</code> 模板的解析主要是通过 <code>parseHTML</code> 函数，它的定义在 <code>src/compiler/parser/html-parser</code> 中：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">parseHTML</span> (<span class="params">html, options</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">let</span> lastTag</span><br><span class="line">  <span class="keyword">while</span> (html) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!lastTag || !isPlainTextElement(lastTag))&#123;</span><br><span class="line">      <span class="keyword">let</span> textEnd = html.indexOf(<span class="string">&#x27;&lt;&#x27;</span>)</span><br><span class="line">      <span class="keyword">if</span> (textEnd === <span class="number">0</span>) &#123;</span><br><span class="line">         <span class="keyword">if</span>(matchComment) &#123;</span><br><span class="line">           advance(commentLength)</span><br><span class="line">           <span class="keyword">continue</span></span><br><span class="line">         &#125;</span><br><span class="line">         <span class="keyword">if</span>(matchDoctype) &#123;</span><br><span class="line">           advance(doctypeLength)</span><br><span class="line">           <span class="keyword">continue</span></span><br><span class="line">         &#125;</span><br><span class="line">         <span class="keyword">if</span>(matchEndTag) &#123;</span><br><span class="line">           advance(endTagLength)</span><br><span class="line">           parseEndTag()</span><br><span class="line">           <span class="keyword">continue</span></span><br><span class="line">         &#125;</span><br><span class="line">         <span class="keyword">if</span>(matchStartTag) &#123;</span><br><span class="line">           parseStartTag()</span><br><span class="line">           handleStartTag()</span><br><span class="line">           <span class="keyword">continue</span></span><br><span class="line">         &#125;</span><br><span class="line">      &#125;</span><br><span class="line">      handleText()</span><br><span class="line">      advance(textLength)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">       handlePlainTextElement()</span><br><span class="line">       parseEndTag()</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上面整体逻辑，循环解析 <code>template</code> ，用正则做各种匹配，对于不同情况分别进行不同的处理，直到整个 template 被解析完毕。 在匹配的过程中会利用 <code>advance</code> 函数不断前进整个模板字符串，直到字符串末尾。</p>
<h3 id="解析流程图"><a href="#解析流程图" class="headerlink" title="解析流程图"></a>解析流程图</h3><p><img src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/parse-20210509231627945.png" alt="img"></p>
<h2 id="optimize-优化器"><a href="#optimize-优化器" class="headerlink" title="optimize 优化器"></a>optimize 优化器</h2><p><code>parse</code> 优化阶段主要做了两件事：</p>
<ul>
<li>在<code>AST</code>中找出所有静态节点并打上标记；</li>
<li>在<code>AST</code>中找出所有静态根节点并打上标记； 优化阶段的源码位于<code>src/compiler/optimizer.js</code>中，如下：</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">optimize</span> (<span class="params">root: ?ASTElement, options: CompilerOptions</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (!root) <span class="keyword">return</span></span><br><span class="line">  isStaticKey = genStaticKeysCached(options.staticKeys || <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">  isPlatformReservedTag = options.isReservedTag || no</span><br><span class="line">  <span class="comment">// 标记静态节点</span></span><br><span class="line">  markStatic(root)</span><br><span class="line">  <span class="comment">// 标记静态根节点</span></span><br><span class="line">  markStaticRoots(root, <span class="literal">false</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="标记静态节点"><a href="#标记静态节点" class="headerlink" title="标记静态节点"></a>标记静态节点</h3><p>从<code>AST</code>中找出所有静态节点，从根节点开始，先标记根节点是否为静态节点，然后看根节点如果是元素节点，那么就去向下递归它的子节点，子节点如果还有子节点那就继续向下递归，直到标记完所有节点。代码如下：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">markStatic</span> (<span class="params">node: ASTNode</span>) </span>&#123;</span><br><span class="line">  node.static = isStatic(node)</span><br><span class="line">  <span class="keyword">if</span> (node.type === <span class="number">1</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (</span><br><span class="line">      !isPlatformReservedTag(node.tag) &amp;&amp;</span><br><span class="line">      node.tag !== <span class="string">&#x27;slot&#x27;</span> &amp;&amp;</span><br><span class="line">      node.attrsMap[<span class="string">&#x27;inline-template&#x27;</span>] == <span class="literal">null</span></span><br><span class="line">    ) &#123;</span><br><span class="line">      <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>, l = node.children.length; i &lt; l; i++) &#123;</span><br><span class="line">      <span class="keyword">const</span> child = node.children[i]</span><br><span class="line">      markStatic(child)</span><br><span class="line">      <span class="keyword">if</span> (!child.static) &#123;</span><br><span class="line">        node.static = <span class="literal">false</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (node.ifConditions) &#123;</span><br><span class="line">      <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">1</span>, l = node.ifConditions.length; i &lt; l; i++) &#123;</span><br><span class="line">        <span class="keyword">const</span> block = node.ifConditions[i].block</span><br><span class="line">        markStatic(block)</span><br><span class="line">        <span class="keyword">if</span> (!block.static) &#123;</span><br><span class="line">          node.static = <span class="literal">false</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在上面代码中，首先调用<code>isStatic</code>函数标记节点是否为静态节点，该函数若返回<code>true</code>表示该节点是静态节点，若返回<code>false</code>表示该节点不是静态节点，函数实现如下：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">isStatic</span> (<span class="params">node: ASTNode</span>): <span class="title">boolean</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (node.type === <span class="number">2</span>) &#123; <span class="comment">// 包含变量的动态文本节点</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (node.type === <span class="number">3</span>) &#123; <span class="comment">// 不包含变量的纯文本节点</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> !!(node.pre || (</span><br><span class="line">    !node.hasBindings &amp;&amp; <span class="comment">// no dynamic bindings</span></span><br><span class="line">    !node.if &amp;&amp; !node.for &amp;&amp; <span class="comment">// not v-if or v-for or v-else</span></span><br><span class="line">    !isBuiltInTag(node.tag) &amp;&amp; <span class="comment">// not a built-in</span></span><br><span class="line">    isPlatformReservedTag(node.tag) &amp;&amp; <span class="comment">// not a component</span></span><br><span class="line">    !isDirectChildOfTemplateFor(node) &amp;&amp;</span><br><span class="line">    <span class="built_in">Object</span>.keys(node).every(isStaticKey)</span><br><span class="line">  ))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>该函数的实现过程其实也说明了如何判断一个节点是否为静态节点。还记得在<code>HTML</code>解析器在调用钩子函数创建<code>AST</code>节点时会根据节点类型的不同为节点加上不同的<code>type</code>属性，用<code>type</code>属性来标记<code>AST</code>节点的节点类型，其对应关系如下：</p>
<table>
<thead>
<tr>
<th align="left">type取值</th>
<th align="left">对应的AST节点类型</th>
</tr>
</thead>
<tbody><tr>
<td align="left">1</td>
<td align="left">元素节点</td>
</tr>
<tr>
<td align="left">2</td>
<td align="left">包含变量的动态文本节点</td>
</tr>
<tr>
<td align="left">3</td>
<td align="left">不包含变量的纯文本节点</td>
</tr>
</tbody></table>
<p>所以在判断一个节点是否为静态节点时首先会根据<code>type</code>值判断节点类型，如果<code>type</code>值为2，那么该节点是包含变量的动态文本节点，它就肯定不是静态节点。</p>
<p>如果元素节点是静态节点，那就必须满足以下几点要求：</p>
<ul>
<li><p>如果节点使用了<code>v-pre</code>指令，那就断定它是静态节点；</p>
</li>
<li><p>如果节点没有使用</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">v-pre</span><br></pre></td></tr></table></figure>

<p>指令，那它要成为静态节点必须满足：</p>
<ul>
<li>不能使用动态绑定语法，即标签上不能有<code>v-</code>、<code>@</code>、<code>:</code>开头的属性；</li>
<li>不能使用<code>v-if</code>、<code>v-else</code>、<code>v-for</code>指令；</li>
<li>不能是内置组件，即标签名不能是<code>slot</code>和<code>component</code>；</li>
<li>标签名必须是平台保留标签，即不能是组件；</li>
<li>当前节点的父节点不能是带有 <code>v-for</code> 的 <code>template</code> 标签；</li>
<li>节点的所有属性的 <code>key</code> 都必须是静态节点才有的 <code>key</code>，注：静态节点的<code>key</code>是有限的，它只能是<code>type</code>,<code>tag</code>,<code>attrsList</code>,<code>attrsMap</code>,<code>plain</code>,<code>parent</code>,<code>children</code>,<code>attrs</code>之一； 标记完当前节点是否为静态节点之后，如果该节点是元素节点，那么还要继续去递归判断它的子节点</li>
</ul>
</li>
</ul>
<p>如果有一个子节点不是静态节点，那么它是父节点也标识为非静态节点</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (node.ifConditions) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">1</span>, l = node.ifConditions.length; i &lt; l; i++) &#123;</span><br><span class="line">        <span class="keyword">const</span> block = node.ifConditions[i].block</span><br><span class="line">        markStatic(block)</span><br><span class="line">        <span class="keyword">if</span> (!block.static) &#123;</span><br><span class="line">            node.static = <span class="literal">false</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="标记静态根节点"><a href="#标记静态根节点" class="headerlink" title="标记静态根节点"></a>标记静态根节点</h3><p>一个节点要想成为静态根节点，它必须满足以下要求：</p>
<ul>
<li>节点本身必须是静态节点；</li>
<li>必须拥有子节点 <code>children</code>；</li>
<li>子节点不能只是只有一个文本节点； 否则的话，对它的优化成本将大于优化后带来的收益。</li>
</ul>
<p>如果当前节点不是静态根节点，那就继续递归遍历它的子节点<code>node.children</code>和<code>node.ifConditions</code>，如下：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (node.children) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>, l = node.children.length; i &lt; l; i++) &#123;</span><br><span class="line">        markStaticRoots(node.children[i], isInFor || !!node.for)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (node.ifConditions) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">1</span>, l = node.ifConditions.length; i &lt; l; i++) &#123;</span><br><span class="line">        markStaticRoots(node.ifConditions[i].block, isInFor)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="generate-代码生成器"><a href="#generate-代码生成器" class="headerlink" title="generate 代码生成器"></a>generate 代码生成器</h2><p>代码生成阶段的源码位于<code>src/compiler/codegen/index.js</code> 中，核心逻辑如下：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">generate</span> (<span class="params">ast,option</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> state = <span class="keyword">new</span> CodegenState(options)</span><br><span class="line">  <span class="keyword">const</span> code = ast ? genElement(ast, state) : <span class="string">&#x27;_c(&quot;div&quot;)&#x27;</span></span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    render: <span class="string">`with(this)&#123;return <span class="subst">$&#123;code&#125;</span>&#125;`</span>,</span><br><span class="line">    staticRenderFns: state.staticRenderFns</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>调用<code>generate</code>函数并传入优化后得到的<code>ast</code>，在<code>generate</code>函数内部先判断<code>ast</code>是否为空，不为空则调用<code>genElement(ast, state)</code>函数创建<code>VNode</code>，为空则创建一个空的元素型<code>div</code>的<code>VNode</code>。然后将得到的结果用<code>with(this)&#123;return $&#123;code&#125;&#125;</code>包裹返回。可以看出，真正起作用的是<code>genElement</code>函数，下面我们继续来看一下<code>genElement</code>函数内部是怎样的。</p>
<p><code>genElement</code>函数定义如下：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">genElement</span> (<span class="params">el: ASTElement, state: CodegenState</span>): <span class="title">string</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (el.staticRoot &amp;&amp; !el.staticProcessed) &#123;</span><br><span class="line">    <span class="keyword">return</span> genStatic(el, state)</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (el.once &amp;&amp; !el.onceProcessed) &#123;</span><br><span class="line">    <span class="keyword">return</span> genOnce(el, state)</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (el.for &amp;&amp; !el.forProcessed) &#123;</span><br><span class="line">    <span class="keyword">return</span> genFor(el, state)</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (el.if &amp;&amp; !el.ifProcessed) &#123;</span><br><span class="line">    <span class="keyword">return</span> genIf(el, state)</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (el.tag === <span class="string">&#x27;template&#x27;</span> &amp;&amp; !el.slotTarget) &#123;</span><br><span class="line">    <span class="keyword">return</span> genChildren(el, state) || <span class="string">&#x27;void 0&#x27;</span></span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (el.tag === <span class="string">&#x27;slot&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> genSlot(el, state)</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// component or element</span></span><br><span class="line">    <span class="keyword">let</span> code</span><br><span class="line">    <span class="keyword">if</span> (el.component) &#123;</span><br><span class="line">      code = genComponent(el.component, el, state)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> data = el.plain ? <span class="literal">undefined</span> : genData(el, state)</span><br><span class="line">      <span class="keyword">const</span> children = el.inlineTemplate ? <span class="literal">null</span> : genChildren(el, state, <span class="literal">true</span>)</span><br><span class="line">      code = <span class="string">`_c(&#x27;<span class="subst">$&#123;el.tag&#125;</span>&#x27;<span class="subst">$&#123;</span></span></span><br><span class="line"><span class="string"><span class="subst">        data ? <span class="string">`,<span class="subst">$&#123;data&#125;</span>`</span> : <span class="string">&#x27;&#x27;</span> <span class="regexp">//</span> data</span></span></span><br><span class="line"><span class="string"><span class="subst">      &#125;</span><span class="subst">$&#123;</span></span></span><br><span class="line"><span class="string"><span class="subst">        children ? <span class="string">`,<span class="subst">$&#123;children&#125;</span>`</span> : <span class="string">&#x27;&#x27;</span> <span class="regexp">//</span> children</span></span></span><br><span class="line"><span class="string"><span class="subst">      &#125;</span>)`</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// module transforms</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; state.transforms.length; i++) &#123;</span><br><span class="line">      code = state.transforms[i](el, code)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> code</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>genElement</code>函数逻辑很清晰，就是根据当前 <code>AST</code> 元素节点属性的不同从而执行不同的代码生成函数。虽然元素节点属性的情况有很多种，但是最后真正创建出来的<code>VNode</code>无非就三种，分别是元素节点，文本节点，注释节点。接下来我们就着重分析一下如何生成这三种节点类型的<code>render</code>函数的。</p>
<h3 id="元素节点"><a href="#元素节点" class="headerlink" title="元素节点"></a>元素节点</h3><p>生成元素型节点的<code>render</code>函数代码如下：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> data = el.plain ? <span class="literal">undefined</span> : genData(el, state)</span><br><span class="line"><span class="keyword">const</span> children = el.inlineTemplate ? <span class="literal">null</span> : genChildren(el, state, <span class="literal">true</span>)</span><br><span class="line">code = <span class="string">`_c(&#x27;<span class="subst">$&#123;el.tag&#125;</span>&#x27;<span class="subst">$&#123;</span></span></span><br><span class="line"><span class="string"><span class="subst">data ? <span class="string">`,<span class="subst">$&#123;data&#125;</span>`</span> : <span class="string">&#x27;&#x27;</span> <span class="regexp">//</span> data</span></span></span><br><span class="line"><span class="string"><span class="subst">&#125;</span><span class="subst">$&#123;</span></span></span><br><span class="line"><span class="string"><span class="subst">children ? <span class="string">`,<span class="subst">$&#123;children&#125;</span>`</span> : <span class="string">&#x27;&#x27;</span> <span class="regexp">//</span> children</span></span></span><br><span class="line"><span class="string"><span class="subst">&#125;</span>)`</span></span><br></pre></td></tr></table></figure>

<p>生成元素节点的<code>render</code>函数就是生成一个<code>_c()</code>函数调用的字符串，上文提到了<code>_c()</code>函数接收三个参数，分别是节点的标签名<code>tagName</code>，节点属性<code>data</code>，节点的子节点列表<code>children</code>。那么我们只需将这三部分都填进去即可。</p>
<ul>
<li>获取节点属性data</li>
</ul>
<p>首先判断<code>plain</code>属性是否为<code>true</code>，若为<code>true</code>则表示节点没有属性，将<code>data</code>赋值为<code>undefined</code>；如果不为<code>true</code>则调用<code>genData</code>函数获取节点属性<code>data</code>数据。<code>genData</code>函数定义如下：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">genData</span> (<span class="params">el: ASTElement, state: CodegenState</span>): <span class="title">string</span> </span>&#123;</span><br><span class="line">  <span class="keyword">let</span> data = <span class="string">&#x27;&#123;&#x27;</span></span><br><span class="line">  <span class="keyword">const</span> dirs = genDirectives(el, state)</span><br><span class="line">  <span class="keyword">if</span> (dirs) data += dirs + <span class="string">&#x27;,&#x27;</span></span><br><span class="line">    <span class="comment">// key</span></span><br><span class="line">    <span class="keyword">if</span> (el.key) &#123;</span><br><span class="line">        data += <span class="string">`key:<span class="subst">$&#123;el.key&#125;</span>,`</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// ref</span></span><br><span class="line">    <span class="keyword">if</span> (el.ref) &#123;</span><br><span class="line">        data += <span class="string">`ref:<span class="subst">$&#123;el.ref&#125;</span>,`</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (el.refInFor) &#123;</span><br><span class="line">        data += <span class="string">`refInFor:true,`</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// pre</span></span><br><span class="line">    <span class="keyword">if</span> (el.pre) &#123;</span><br><span class="line">        data += <span class="string">`pre:true,`</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 篇幅所限，省略其他情况的判断</span></span><br><span class="line">    data = data.replace(<span class="regexp">/,$/</span>, <span class="string">&#x27;&#x27;</span>) + <span class="string">&#x27;&#125;&#x27;</span></span><br><span class="line">    <span class="keyword">return</span> data</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在拼接字符串，先给<code>data</code>赋值为一个<code>&#123;</code>，然后判断存在哪些属性数据，就将这些数据拼接到<code>data</code>中，最后再加一个<code>&#125;</code>，最终得到节点全部属性<code>data</code>。</p>
<ul>
<li>获取子节点列表children</li>
</ul>
<p>获取子节点列表<code>children</code>其实就是遍历<code>AST</code>的<code>children</code>属性中的元素，然后根据元素属性的不同生成不同的<code>VNode</code>创建函数调用字符串，如下：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">genChildren</span> (<span class="params">el</span>):  </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (children.length) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">`[<span class="subst">$&#123;children.map(c =&gt; genNode(c, state)).join(<span class="string">&#x27;,&#x27;</span>)&#125;</span>]`</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">genNode</span> (<span class="params">node: ASTNode, state: CodegenState</span>): <span class="title">string</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (node.type === <span class="number">1</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> genElement(node, state)</span><br><span class="line">  &#125; <span class="keyword">if</span> (node.type === <span class="number">3</span> &amp;&amp; node.isComment) &#123;</span><br><span class="line">    <span class="keyword">return</span> genComment(node)</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> genText(node)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li>上面两步完成之后，生成<code>_c（）</code>函数调用字符串，如下：</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">code = <span class="string">`_c(&#x27;<span class="subst">$&#123;el.tag&#125;</span>&#x27;<span class="subst">$&#123;</span></span></span><br><span class="line"><span class="string"><span class="subst">        data ? <span class="string">`,<span class="subst">$&#123;data&#125;</span>`</span> : <span class="string">&#x27;&#x27;</span> <span class="regexp">//</span> data</span></span></span><br><span class="line"><span class="string"><span class="subst">      &#125;</span><span class="subst">$&#123;</span></span></span><br><span class="line"><span class="string"><span class="subst">        children ? <span class="string">`,<span class="subst">$&#123;children&#125;</span>`</span> : <span class="string">&#x27;&#x27;</span> <span class="regexp">//</span> children</span></span></span><br><span class="line"><span class="string"><span class="subst">      &#125;</span>)`</span></span><br></pre></td></tr></table></figure>

<h3 id="文本节点"><a href="#文本节点" class="headerlink" title="文本节点"></a>文本节点</h3><p>文本型的<code>VNode</code>可以调用<code>_v(text)</code>函数来创建，所以生成文本节点的<code>render</code>函数就是生成一个<code>_v(text)</code>函数调用的字符串。<code>_v()</code>函数接收文本内容作为参数，如果文本是动态文本，则使用动态文本<code>AST</code>节点的<code>expression</code>属性，如果是纯静态文本，则使用<code>text</code>属性。其生成代码如下：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">genText</span> (<span class="params">text: ASTText | ASTExpression</span>): <span class="title">string</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">`_v(<span class="subst">$&#123;text.type === <span class="number">2</span></span></span></span><br><span class="line"><span class="string"><span class="subst">    ? text.expression <span class="regexp">//</span> no need <span class="keyword">for</span> () because already wrapped <span class="keyword">in</span> _s()</span></span></span><br><span class="line"><span class="string"><span class="subst">    : transformSpecialNewlines(<span class="built_in">JSON</span>.stringify(text.text))</span></span></span><br><span class="line"><span class="string"><span class="subst">  &#125;</span>)`</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="注释节点"><a href="#注释节点" class="headerlink" title="注释节点"></a>注释节点</h3><p>注释型的<code>VNode</code>可以调用<code>_e(text)</code>函数来创建，所以生成注释节点的<code>render</code>函数就是生成一个<code>_e(text)</code>函数调用的字符串。<code>_e()</code>函数接收注释内容作为参数，其生成代码如下：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">genComment</span> (<span class="params">comment: ASTText</span>): <span class="title">string</span> </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">`_e(<span class="subst">$&#123;<span class="built_in">JSON</span>.stringify(comment.text)&#125;</span>)`</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="模板编译的整体流程"><a href="#模板编译的整体流程" class="headerlink" title="模板编译的整体流程"></a>模板编译的整体流程</h2><p>最开始调用的<code>compileToFunctions</code>函数内部调用了<code>compile</code>函数，在<code>compile</code>函数内部又调用了<code>baseCompile</code>函数，而<code>baseCompile</code>函数返回的是代码生成阶段生成好的<code>render</code>函数字符串。所以在<code>compileToFunctions</code>函数内部调用<code>compile</code>函数就可以拿到生成好的<code>render</code>函数字符串，然后在<code>compileToFunctions</code>函数内部将<code>render</code>函数字符串传给<code>createFunction</code>函数从而变成真正的<code>render</code>函数返回出去，最后将其赋值给<code>options.render</code></p>
<p><img src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/14ebb3e5a563e3b7a392a05d21cc7b5b.jpeg" alt="总结 - 图1"></p>
<h2 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h2><ul>
<li><p><a target="_blank" rel="noopener" href="https://www.bookset.io/read/Learn-Vue-Source-Code/047cbc3ffd2f606c.md">https://www.bookset.io/read/Learn-Vue-Source-Code/047cbc3ffd2f606c.md</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="https://ustbhuangyi.github.io/vue-analysis/v2/compile/">https://ustbhuangyi.github.io/vue-analysis/v2/compile/</a></p>
</li>
</ul>
</article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">文章作者: </span><span class="post-copyright-info"><a href="mailto:undefined">志非</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">文章链接: </span><span class="post-copyright-info"><a href="https://fif.gitee.io/blog/2021/04/30/vue/vue-code-7-compiler/">https://fif.gitee.io/blog/2021/04/30/vue/vue-code-7-compiler/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="https://fif.gitee.io/blog" target="_blank">Jeffery Blog</a>！</span></div></div><div class="tag_share"><div class="post-meta__tag-list"><a class="post-meta__tags" href="/blog/tags/vue/">vue</a><a class="post-meta__tags" href="/blog/tags/vue-%E6%BA%90%E7%A0%81/">vue 源码</a></div><div class="post_share"><div class="social-share" data-image="https://gitee.com/FIF/pic-beg/raw/master/images/vue/vuetemplate.jpg" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/social-share.js/dist/css/share.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/social-share.js/dist/js/social-share.min.js" defer></script></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/blog/2021/05/04/vue/vue-code-8.component/"><img class="prev-cover" src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/components.png" onerror="onerror=null;src='/blog/img/404.jpg'" alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">（Vue源码六）Vue 组件原理分析</div></div></a></div><div class="next-post pull-right"><a href="/blog/2021/04/27/vue/vue-code-6.reactive/"><img class="next-cover" src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/mvvm.png" onerror="onerror=null;src='/blog/img/404.jpg'" alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">（Vue源码四）数据响应式原理</div></div></a></div></nav><div class="relatedPosts"><div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span> 相关推荐</span></div><div class="relatedPosts-list"><div><a href="/blog/2021/04/12/vue/vue-code-3.build/" title="（Vue源码一）Vue 源码设计和构建流程"><img class="cover" src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/Vue.js-i18n-768x512.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2021-04-12</div><div class="title">（Vue源码一）Vue 源码设计和构建流程</div></div></a></div><div><a href="/blog/2021/04/17/vue/vue-code-4.debugger/" title="（Vue源码二）Vue实例挂载流程"><img class="cover" src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/1_b2Ig4HR2UOQEV5I2QnQrrw.png" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2021-04-17</div><div class="title">（Vue源码二）Vue实例挂载流程</div></div></a></div><div><a href="/blog/2021/04/23/vue/vue-code-5.mergeOptions/" title="（Vue源码三）Vue 丰富的选项合并策略"><img class="cover" src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/vue-highcharts_1200.webp" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2021-04-23</div><div class="title">（Vue源码三）Vue 丰富的选项合并策略</div></div></a></div><div><a href="/blog/2021/05/04/vue/vue-code-8.component/" title="（Vue源码六）Vue 组件原理分析"><img class="cover" src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/components.png" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2021-05-04</div><div class="title">（Vue源码六）Vue 组件原理分析</div></div></a></div><div><a href="/blog/2021/04/27/vue/vue-code-6.reactive/" title="（Vue源码四）数据响应式原理"><img class="cover" src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/mvvm.png" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2021-04-27</div><div class="title">（Vue源码四）数据响应式原理</div></div></a></div><div><a href="/blog/2021/05/14/vue/vue-code-9.event/" title="（Vue源码七）Vue 事件"><img class="cover" src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/components.png" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2021-05-14</div><div class="title">（Vue源码七）Vue 事件</div></div></a></div></div></div><hr/><div id="post-comment"><div class="comment-head"><div class="comment-headline"><i class="fas fa-comments fa-fw"></i><span> 评论</span></div></div><div class="comment-wrap"><div><div class="vcomment" id="vcomment"></div></div></div></div></div><div class="aside-content" id="aside-content"><div class="card-widget card-info"><div class="card-info-avatar is-center"><img class="avatar-img" src= "" data-lazy-src="/blog/img/home/avatar.jpeg" onerror="this.onerror=null;this.src='/blog/img/friend_404.gif'" alt="avatar"/><div class="author-info__name">志非</div><div class="author-info__description">自己变得更好，生活才会更美好</div></div><div class="card-info-data"><div class="card-info-data-item is-center"><a href="/blog/archives/"><div class="headline">文章</div><div class="length-num">12</div></a></div><div class="card-info-data-item is-center"><a href="/blog/tags/"><div class="headline">标签</div><div class="length-num">6</div></a></div><div class="card-info-data-item is-center"><a href="/blog/categories/"><div class="headline">分类</div><div class="length-num">7</div></a></div></div><a class="button--animated" id="card-info-btn" target="_blank" rel="noopener" href="https://github.com/JefferyXZF"><i class="fab fa-github"></i><span>Follow Me</span></a><div class="card-info-social-icons is-center"><a class="social-icon" href="https://github.com/JefferyXZF" target="_blank" title="Github"><i class="fab fa-github"></i></a><a class="social-icon" href="https://www.yuque.com/jeffery-kkrpn/uz24n9" target="_blank" title="语雀"><i class="fa fa-folder-open"></i></a><a class="social-icon" href="https://mail.qq.com/" target="_blank" title="Email"><i class="fas fa-envelope"></i></a></div></div><div class="card-widget card-announcement"><div class="item-headline"><i class="fas fa-bullhorn card-announcement-animation"></i><span>公告</span></div><div class="announcement_content">越努力越幸运</div></div><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span></div><div class="toc-content"><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%A6%82%E5%BF%B5"><span class="toc-number">1.</span> <span class="toc-text">概念</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%A8%A1%E6%9D%BF%E6%B8%B2%E6%9F%93%E8%BF%87%E7%A8%8B"><span class="toc-number">2.</span> <span class="toc-text">模板渲染过程</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%BC%96%E8%AF%91%E5%85%A5%E5%8F%A3"><span class="toc-number">3.</span> <span class="toc-text">编译入口</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%85%B7%E4%BD%93%E6%B5%81%E7%A8%8B"><span class="toc-number">4.</span> <span class="toc-text">具体流程</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#parse-%E8%A7%A3%E6%9E%90%E5%99%A8"><span class="toc-number">5.</span> <span class="toc-text">parse 解析器</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#parse-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90"><span class="toc-number">5.1.</span> <span class="toc-text">parse 源码分析</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#parseHTML-%EF%BC%88HTML%E8%A7%A3%E6%9E%90%E5%99%A8%EF%BC%89"><span class="toc-number">5.2.</span> <span class="toc-text">parseHTML （HTML解析器）</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%A7%A3%E6%9E%90%E6%B5%81%E7%A8%8B%E5%9B%BE"><span class="toc-number">5.3.</span> <span class="toc-text">解析流程图</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#optimize-%E4%BC%98%E5%8C%96%E5%99%A8"><span class="toc-number">6.</span> <span class="toc-text">optimize 优化器</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%A0%87%E8%AE%B0%E9%9D%99%E6%80%81%E8%8A%82%E7%82%B9"><span class="toc-number">6.1.</span> <span class="toc-text">标记静态节点</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%A0%87%E8%AE%B0%E9%9D%99%E6%80%81%E6%A0%B9%E8%8A%82%E7%82%B9"><span class="toc-number">6.2.</span> <span class="toc-text">标记静态根节点</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#generate-%E4%BB%A3%E7%A0%81%E7%94%9F%E6%88%90%E5%99%A8"><span class="toc-number">7.</span> <span class="toc-text">generate 代码生成器</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%85%83%E7%B4%A0%E8%8A%82%E7%82%B9"><span class="toc-number">7.1.</span> <span class="toc-text">元素节点</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%96%87%E6%9C%AC%E8%8A%82%E7%82%B9"><span class="toc-number">7.2.</span> <span class="toc-text">文本节点</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%B3%A8%E9%87%8A%E8%8A%82%E7%82%B9"><span class="toc-number">7.3.</span> <span class="toc-text">注释节点</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%A8%A1%E6%9D%BF%E7%BC%96%E8%AF%91%E7%9A%84%E6%95%B4%E4%BD%93%E6%B5%81%E7%A8%8B"><span class="toc-number">8.</span> <span class="toc-text">模板编译的整体流程</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%8F%82%E8%80%83%E6%96%87%E7%AB%A0"><span class="toc-number">9.</span> <span class="toc-text">参考文章</span></a></li></ol></div></div><script defer="defer" data-pjax="data-pjax" src="/blog/js/card_clock.js"></script><div class="card-widget card-clock"><div id="clock"><img v-if="clockshow == &quot;false&quot;" alt="" src= "" data-lazy-src="/blog/img/weather/loading.gif" height="120px" width="100%"/><table class="clock" v-if="clockshow"><tbody><tr><td class="clockdate">{{date}}</td><td class="weatherimg"><img id="weatherimg" :src="weatherimg" alt=""/></td><td class="temperature">{{temperature}}</td><td class="humidityimg"><img id="humidityimg" :src="humidityimg" alt=""/></td><td class="humidity">{{humidity}}</td></tr><tr class="time"><td colspan="5">{{time}}</td></tr><tr><td class="usaqi" colspan="1"><span>{{ip}}</span></td><td class="city" colspan="2">{{city}}</td><td class="daylight" colspan="2">{{daylight}}</td></tr></tbody></table></div></div><div class="card-widget card-recent-post"><div class="item-headline"><i class="fas fa-history"></i><span>最新文章</span></div><div class="aside-list"><div class="aside-list-item"><a class="thumbnail" href="/blog/2021/05/24/tools/hexo/advanced/" title="Hexo 博客页面性能优化"><img src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/Hexo-logo.png" onerror="this.onerror=null;this.src='/blog/img/404.jpg'" alt="Hexo 博客页面性能优化"/></a><div class="content"><a class="title" href="/blog/2021/05/24/tools/hexo/advanced/" title="Hexo 博客页面性能优化">Hexo 博客页面性能优化</a><time datetime="2021-05-23T16:00:00.000Z" title="发表于 2021-05-24 00:00:00">2021-05-24</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/blog/2021/05/14/vue/vue-code-9.event/" title="（Vue源码七）Vue 事件"><img src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/components.png" onerror="this.onerror=null;this.src='/blog/img/404.jpg'" alt="（Vue源码七）Vue 事件"/></a><div class="content"><a class="title" href="/blog/2021/05/14/vue/vue-code-9.event/" title="（Vue源码七）Vue 事件">（Vue源码七）Vue 事件</a><time datetime="2021-05-13T16:00:00.000Z" title="发表于 2021-05-14 00:00:00">2021-05-14</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/blog/2021/05/04/vue/vue-code-8.component/" title="（Vue源码六）Vue 组件原理分析"><img src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/components.png" onerror="this.onerror=null;this.src='/blog/img/404.jpg'" alt="（Vue源码六）Vue 组件原理分析"/></a><div class="content"><a class="title" href="/blog/2021/05/04/vue/vue-code-8.component/" title="（Vue源码六）Vue 组件原理分析">（Vue源码六）Vue 组件原理分析</a><time datetime="2021-05-03T16:00:00.000Z" title="发表于 2021-05-04 00:00:00">2021-05-04</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/blog/2021/04/30/vue/vue-code-7-compiler/" title="（Vue源码五）Vue 编译原理"><img src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/vuetemplate.jpg" onerror="this.onerror=null;this.src='/blog/img/404.jpg'" alt="（Vue源码五）Vue 编译原理"/></a><div class="content"><a class="title" href="/blog/2021/04/30/vue/vue-code-7-compiler/" title="（Vue源码五）Vue 编译原理">（Vue源码五）Vue 编译原理</a><time datetime="2021-04-29T16:00:00.000Z" title="发表于 2021-04-30 00:00:00">2021-04-30</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/blog/2021/04/27/vue/vue-code-6.reactive/" title="（Vue源码四）数据响应式原理"><img src= "" data-lazy-src="https://gitee.com/FIF/pic-beg/raw/master/images/vue/mvvm.png" onerror="this.onerror=null;this.src='/blog/img/404.jpg'" alt="（Vue源码四）数据响应式原理"/></a><div class="content"><a class="title" href="/blog/2021/04/27/vue/vue-code-6.reactive/" title="（Vue源码四）数据响应式原理">（Vue源码四）数据响应式原理</a><time datetime="2021-04-26T16:00:00.000Z" title="发表于 2021-04-27 00:00:00">2021-04-27</time></div></div></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">&copy;2021 By 志非</div><div class="footer_custom_text">Hi, welcome to my <a href="/">blog</a>!</div><div id="workboard"></div><script async="async" src="/blog/js/runtime.js"></script></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="translateLink" type="button" title="简繁转换">繁</button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside_config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><button id="chat_btn" type="button" title="rightside.chat_btn"><i class="fas fa-sms"></i></button><a id="to_comment" href="#post-comment" title="直达评论"><i class="fas fa-comments"></i></a><button id="go-up" type="button" title="回到顶部"><i class="fas fa-arrow-up"></i></button></div></div><div id="algolia-search"><div class="search-dialog"><div class="search-dialog__title" id="algolia-search-title">Algolia</div><div id="algolia-input-panel"><div id="algolia-search-input"></div></div><hr/><div id="algolia-search-results"><div id="algolia-hits"></div><div id="algolia-pagination"></div><div id="algolia-stats"></div></div><span class="search-close-button"><i class="fas fa-times"></i></span></div><div id="search-mask"></div></div><div><script src="https://cdn.jsdelivr.net/npm/vue"></script><script src="/blog/js/utils.js"></script><script src="/blog/js/main.js"></script><script src="/blog/js/tw_cn.js"></script><script src="https://cdn.jsdelivr.net/npm/medium-zoom/dist/medium-zoom.min.js"></script><script src="https://cdn.jsdelivr.net/npm/instant.page/instantpage.min.js" type="module"></script><script src="https://cdn.jsdelivr.net/npm/vanilla-lazyload/dist/lazyload.iife.min.js"></script><script src="https://cdn.jsdelivr.net/npm/node-snackbar/dist/snackbar.min.js"></script><script src="/blog/js/search/algolia.js"></script><script>var preloader = {
  endLoading: () => {
    document.body.style.overflow = 'auto';
    document.getElementById('loading-box').classList.add("loaded")
  },
  initLoading: () => {
    document.body.style.overflow = '';
    document.getElementById('loading-box').classList.remove("loaded")

  }
}
window.addEventListener('load',preloader.endLoading())</script><div class="js-pjax"><script>if (document.getElementsByClassName('mermaid').length) {
  if (window.mermaidJsLoad) mermaid.init()
  else {
    getScript('https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js').then(() => {
      window.mermaidJsLoad = true
      mermaid.initialize({
        theme: 'default',
      })
      false && mermaid.init()
    })
  }
}</script><script>function loadValine () {
  function initValine () {
    const valine = new Valine(Object.assign({
      el: '#vcomment',
      appId: 'JC72d1A7XBwC26rGCKMVn5CS-MdYXbMMI',
      appKey: 'xI6bG7FseICz8ShKdmWVe7N1',
      placeholder: 'Please leave your footprints',
      avatar: 'https://gitee.com/FIF/pic-beg/raw/master/images/vue/1619248532079-avatar_coment.webp',
      meta: 'nick,mail,link'.split(','),
      pageSize: '10',
      lang: 'zh-CN',
      recordIP: false,
      serverURLs: '',
      emojiCDN: '',
      emojiMaps: "",
      enableQQ: true,
      path: window.location.pathname,
      requiredFields: ["nick,mail"],
      visitor: false,
      avatar_cdn: 'https://www.gravatar.com/avatar/'
    }, null))
  }

  if (typeof Valine === 'function') initValine() 
  else getScript('https://cdn.jsdelivr.net/npm/valine/dist/Valine.min.js').then(initValine)
}

if ('Valine' === 'Valine' || !true) {
  if (true) btf.loadComment(document.getElementById('vcomment'),loadValine)
  else setTimeout(loadValine, 0)
} else {
  function loadOtherComment () {
    loadValine()
  }
}</script></div><div class="aplayer no-destroy" data-id="000PeZCQ1i4XVs" data-server="tencent" data-type="artist" data-fixed="true" data-mini="true" data-listFolded="false" data-order="random" data-preload="none" data-autoplay="false" pjax="true" muted></div><script defer="defer" id="ribbon" src="https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/dist/canvas-ribbon.min.js" size="150" alpha="0.6" zIndex="-1" mobile="false" data-click="true"></script><script id="canvas_nest" defer="defer" color="0,0,255" opacity="0.7" zIndex="-1" count="99" mobile="false" src="https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/dist/canvas-nest.min.js"></script><script src="https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/dist/activate-power-mode.min.js"></script><script>POWERMODE.colorful = true;
POWERMODE.shake = true;
POWERMODE.mobile = false;
document.body.addEventListener('input', POWERMODE);
</script><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.js"></script><script src="https://cdn.jsdelivr.net/gh/metowolf/MetingJS@1.2/dist/Meting.min.js"></script><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div></body></html>